package org.raymond.iworks.study.netty.nio;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * @author raymond
 * @version V1.0
 * @Description: TODO
 */
public class ChannelTest {
    /**
     * 常用的channel
     * FileChannel: 读写文件中的数据
     * SocketChannel: 通过TCP读写网络中的数据
     * ServerSockectChannel: 监听新进来的TCP连接,像Web服务器那样.对每一个新进来的连接都会创建一个SocketChannel.
     * DatagramChannel: 通过UDP读写网络中的数据
     *
     * Channel本身并不存储数据,只是负责数据的运输.必须要和Buffer一起使用
     */
    public static void main(String[] args) throws Exception {
        gatheringAndScatteringByteTest();
    }

    public static void newFile() throws Exception {
        String msg = "学习NIO: bytebuffer!";
        byte[] bytes = msg.getBytes();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(bytes);

        File to = new File("tmp/2.txt");
        FileOutputStream tos = new FileOutputStream(to);
        FileChannel tosChannel = tos.getChannel();
        tosChannel.write(byteBuffer);

        tos.close();
        tosChannel.close();
    }

    public static void copyFileTest() throws Exception {
        File from = new File("tmp/1.txt");
        File to = new File("tmp/2.txt");
        FileInputStream fis = new FileInputStream(from);
        FileOutputStream tos = new FileOutputStream(to);
        // 从流获取通道
        FileChannel fisChannel = fis.getChannel();
        FileChannel tosChannel = tos.getChannel();
        // 创建一个bytebuffer, 小文件直接一次读完
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)from.length());
        // 把输入流通道的数据读入缓冲区
        fisChannel.read(byteBuffer);
        // 切换成读模式
        byteBuffer.flip();
        // 把数据从缓冲区写入到输出流通道
        tosChannel.write(byteBuffer);
        // 回收资源
        tos.close();
        fis.close();
        tosChannel.close();
        fisChannel.close();
    }

    public static void socketChannelTest() throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 12000);
        ssc.bind(address);
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while(true){
            SocketChannel sc = ssc.accept();
            while(sc.read(byteBuffer)!=-1){
                // 用telnet测试
                System.out.println(new String(byteBuffer.array()));
                byteBuffer.clear();
            }
        }
    }

    /**
     * transferTo: 把源通道的数据传输到目的通道中
     */
    public static void transferToTest() throws IOException {
        File from = new File("tmp/1.txt");
        File to = new File("tmp/2.txt");
        FileInputStream fis = new FileInputStream(from);
        FileOutputStream tos = new FileOutputStream(to);
        // 从流获取通道
        FileChannel fisChannel = fis.getChannel();
        FileChannel tosChannel = tos.getChannel();
        // 把输入流通道的数据传输到输出流通道中
        fisChannel.transferTo(0, from.length(), tosChannel);

        // 回收资源
        tos.close();
        fis.close();
        tosChannel.close();
        fisChannel.close();
    }

    /**
     * transferFrom: 把来自源通道的数据传输到目的通道中
     */
    public static void transferFromTest() throws IOException {
        File from = new File("tmp/1.txt");
        File to = new File("tmp/2.txt");
        FileInputStream fis = new FileInputStream(from);
        FileOutputStream tos = new FileOutputStream(to);
        // 从流获取通道
        FileChannel fisChannel = fis.getChannel();
        FileChannel tosChannel = tos.getChannel();
        // 把输入流通道的数据传输到输出流通道中
        tosChannel.transferFrom(fisChannel, 0, from.length());

        // 回收资源
        tos.close();
        fis.close();
        tosChannel.close();
        fisChannel.close();
    }

    /**
     * 可以看到循环了两次.第一次循环时,三个缓冲区都读取了5个字节,总共读取了15,也就是读满了.还剩下11个字节,于是第二次循环时,
     * 前两个缓冲区分配了5个字节,最后一个缓冲区给他分配了1个字节,刚好读完.总共就是26个字节.
     * 这就是分散读取,聚合写入的过程.
     * 使用场景就是可以使用一个缓冲区数组,自动地根据需要去分配缓冲区的大小.可以减少内存消耗.网络IO也可以使用.
     * @throws IOException
     */
    public static void gatheringAndScatteringByteTest() throws IOException {
        File from = new File("tmp/1.txt");
        File to = new File("tmp/2.txt");
        FileInputStream fis = new FileInputStream(from);
        FileOutputStream tos = new FileOutputStream(to);
        // 从流获取通道
        FileChannel fisChannel = fis.getChannel();
        FileChannel tosChannel = tos.getChannel();
        // 创建3个缓冲区
        ByteBuffer bf1 = ByteBuffer.allocate(5);
        ByteBuffer bf2 = ByteBuffer.allocate(5);
        ByteBuffer bf3 = ByteBuffer.allocate(5);
        // 创建一个缓冲区数组
        ByteBuffer[] bufArr = new ByteBuffer[]{bf1, bf2, bf3};
        long read = 0;
        long total = 0;
        // 分散读取
        while((read=fisChannel.read(bufArr))!=-1){
            total += read;
            // 打印位置
            Arrays.stream(bufArr)
                    .map(buf->String.format("pos:%s, limit:%s", buf.position(), buf.limit()))
                    .forEach(System.out::println);
            // 切换模式
            Arrays.stream(bufArr).forEach(ByteBuffer::flip);
            // 聚合写入
            tosChannel.write(bufArr);
            // 清空缓冲区
            Arrays.stream(bufArr).forEach(ByteBuffer::clear);
        }
        System.out.println("总长度:"+total);
        // 回收资源
        tos.close();
        fis.close();
        tosChannel.close();
        fisChannel.close();
    }
}
